(ns
  com.marzhillstudios.molehill.file
  (:import [com.petebevin.markdown MarkdownProcessor]
           [org.joda.time.format DateTimeFormat]
           [org.joda.time DateTime]
           [java.io BufferedReader])
  (:require [clojure.contrib.str-utils2 :as s])
  (:use [com.marzhillstudios.util :only [defmulti- add-type]]
        [com.marzhillstudios.molehill.log-util]
        [com.marzhillstudios.path]
        [clojure.contrib.duck-streams :only [file-str with-in-reader with-out-writer]]))

(defstruct hill-file :title :content :date :tags :author :epoch)
(defstruct date-with-format :date :format)
(def hill-file-type ::hill-file)

(defn is-hill-file?
  [file]
  (isa? (type file) hill-file-type))

; TODO(jwall): move to a util?
(defn mk-date-struct [date fmt]
  (add-type ::formatted-date
            (struct-map date-with-format :date date :format fmt)))

(defn mk-now-date
  []
 (let [now (DateTime.)]
   (mk-date-struct
     (.print (DateTimeFormat/forPattern "yyyy-MM-dd") (DateTime.))
     "yyyy-MM-dd")))

; TODO(jwall): move to a util?
;; util functions
(defmulti- str-datetime->epoch type)
(defmethod str-datetime->epoch java.lang.String
  [formatted] (str-datetime->epoch (mk-date-struct
                                    formatted
                                    "yyyy-MM-dd HH:mm:ss")))
(defmethod str-datetime->epoch clojure.lang.PersistentArrayMap
  [formatted] (str-datetime->epoch (mk-date-struct
                                    (:date formatted)
                                    (:format formatted))))
(defmethod str-datetime->epoch ::formatted-date
  [formatted] (.getMillis (.parseDateTime
                            (DateTimeFormat/forPattern
                              (:format formatted))
                            (:date formatted))))

(defmulti- str-datetime->date type)
(defmethod str-datetime->date java.lang.String
  [formatted] (str-datetime->date (mk-date-struct
                                    formatted
                                    "yyyy-MM-dd HH:mm:ss")))
(defmethod str-datetime->date clojure.lang.PersistentArrayMap
  [formatted] (str-datetime->date (mk-date-struct
                                    (:date formatted)
                                    (:format formatted))))
(defmethod str-datetime->date ::formatted-date
  [formatted] (let [dateTime (.parseDateTime
                            (DateTimeFormat/forPattern
                              (:format formatted))
                            (:date formatted))]
                (s/join "-" [(.getYear dateTime)
                     (.getMonthOfYear dateTime)
                     (.getDayOfMonth dateTime)])))

; TODO(jwall): unit-tests
(defn sanitize-text
  [text]
  (s/replace
         (s/trim (s/replace text #"(\"|'|;|\.|!|,|&|$|#|@|%|=|\(|\)|\?|\^|\*|\+)+" ""))
         #"(\\|/| |--*|_|::?|\|\|?)+" "-"))

(defn content->markdown
  [content]
  (.markdown (MarkdownProcessor.) content))

(defn add-parsed-content
  [hill-file]
  (assoc hill-file :parsed-content
         (fn [] (content->markdown (:content hill-file)))))

(defn new-hill-file
  ([title]
   (new-hill-file title "" (mk-now-date) nil nil))
  ([title content]
   (new-hill-file title content (mk-now-date) nil nil))
  ([title content date tags author]
   (add-type hill-file-type
             (add-parsed-content
              (struct-map hill-file :title title
                          :content content :date date
                          :tags (vec (map #(sanitize-text
                                            (.toLowerCase (str %))) tags))
                          :author author :epoch (str-datetime->epoch date))))))

(defn meta-file-name
  [filename] (str "." filename ".clj"))

;; hill-file struct accessor functions
(defn hill-file-slug [fstruct]
  "Returns a slug for a hill file using the title."
  (.toLowerCase (str (sanitize-text (:title fstruct)))))

(defn hill-file-meta [fstruct]
  {:title (:title fstruct)
   :date (:date fstruct)
   :epoch (:epoch fstruct)
   :tags (vec (:tags fstruct))
   :author (:author fstruct)})

;; IO
; TODO(jwall): unit-test that this preserves newlines
(defn- read-all
  ([] (read-all *in*))
  ([rdr]
    (apply str (mapcat #(str %1 "\n") (line-seq (BufferedReader. rdr))))))

(defn hill-file<-disk
  ([filename] (hill-file<-disk (dirname filename) (basename filename)))
  ([root filename]
  (debug-fmt "filename: [%s] argument type: [%s]" filename (type filename))
  (debug-fmt "root: [%s] argument type: [%s]" root (type root))
  (with-in-reader (file-str root "/" filename)
    (let [file-meta (read)
          file-contents (read-all)]
    (debug-fmt "file meta: %s" file-meta)
    (new-hill-file (:title file-meta) file-contents
                   (:date file-meta) (:tags file-meta)
                   (:author file-meta))))))

; TODO(jwall): pretty printing the clojure header would be nice
(defn hill-file->disk
  ([root hill-file]
  (let [file-slug (hill-file-slug hill-file)
        file (str root "/" file-slug)]
    (debug-fmt "writing out %s" file)
    (with-out-writer file
                     (prn (hill-file-meta hill-file))
                     (print (:content hill-file)))
    file)))

(defn files-from-list
  ([entries]
    (assert (not (nil? entries)))
   (debug-fmt "path: [%s] root: [%s] file: [%s]"
             (first entries)
             (dirname (first entries))
             (basename (first entries)))
   (map #(hill-file<-disk (dirname %1) (basename %1)) entries)))

